//
//  AsyncDowload.swift
//  soapp_Example
//
//  Created by zhuxietong on 2019/4/23.
//  Copyright © 2019 CocoaPods. All rights reserved.
//

import UIKit
import Alamofire
import soapp
import Eelay

//extension Array where Element: DownloadObserver {
//    
//    mutating func remove(observer object: DownloadObserver) {
//        
//        if let index = firstIndex(where: { (ele:DownloadObserver) -> Bool in return ele === object})
//        {
//            remove(at: index)
//        }
//        
//    }
//    
//    mutating func append(observer object: DownloadObserver) {
//        if let _ = firstIndex(where: { (ele:DownloadObserver) -> Bool in return ele === object})
//        {
//        
//        }else{
//            
//        }
//    }
//}

extension Download{
    
    public enum DownloadError:Error{
        case invalidURL
        case downloadError(message:String)
    }
    
    public enum Status {
        case waiting
        case processing(progress:NumberValue)
        case stoped
        case success(code:Int,message:String)//已存在code = 1; 下载成功 code 2
        case failed(error:DownloadError)
    }
    
}
public class DownloadFile {
    struct Keys{
        static let name = "name"
        static var remoteURL = "remoteURL"
        static var locURL = "locURL"
        static var ID = "ID"
        static var progress = "progress"
        static var statusJson = "statusJson"
        static var status = "status"
        static var cover = "cover"
    }
    
    public var name:String = ""
    public var remoteURL:String = ""
    public var locURL:String = ""
    public var ID:String = ""
    public var cover:String = ""
    public var progress:String = "0"
    
    public var status:Download.Status = .waiting
    
    public var statusJson:String = "{}"
    public var createTime:String = Date()["STAMP"]

    
    private func initStatus(){
        var  _status = Download.Status.waiting
        let info = statusJson.JsonDictionary ?? NSMutableDictionary()
        switch info[Keys.status,""] {
        case "waiting":
            _status = .waiting
        case "processing":
            _status = .processing(progress: info[float:"progress",0])
        case "stoped":
            _status = .stoped
        case "success":
            _status = .success(code: info[int:"code",1], message: info["message",""])
        case "failed":
            _status = .failed(error: Download.DownloadError.downloadError(message: info["error",""]))
        default: break
            
        }
        self.status = _status
    }
    
    private func saveStatus(){
        switch self.status {
        case .waiting:
            statusJson = [Keys.status:"waiting"].rawJson ?? "{}"
        case .failed(error: let error):
            switch error {
            case .downloadError(message: let msg):
                statusJson = [Keys.status:"failed","error":msg].rawJson ?? "{}"
            case .invalidURL:
                statusJson = [Keys.status:"failed","error":"invalidURL"].rawJson ?? "{}"
            }
        case .processing(progress: let num):
            statusJson = [Keys.status:"processing","progress":num.doubleValue].rawJson ?? "{}"
        case .stoped:
            statusJson = [Keys.status:"stoped"].rawJson ?? "{}"
        case .success(code: let code, message: let msg):
            statusJson = [Keys.status:"success","message":msg,"code":code].rawJson ?? "{}"
        }
        
    }
    
    
    public var floatProgress:Float{
        get{
            return Float(self.progress) ?? 0
        }
    }
    
    
    public var cacheURL:URL{
        get{
            let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            let fileURL = documentsURL.appendingPathComponent("\(Download.downloadPath)_cache/\(ID)")
            return fileURL
        }
    }
    
    
    public static func locFile(remoteURL:String)->DownloadFile?{
        var file:DownloadFile? = nil
        let files = Download.files()
        for one in files {
            if one.remoteURL == remoteURL{

                file = one
            }
        }
        return file
    }
    
    public var dotName:String{
        get{
            return Download.extensionName(url: locURL)
        }
    }
    
    public var createDate:Date{
        get{
            return stampToTime(self.createTime)
        }
    }
    
    public init() {
        self.name = Date()["STAMP"]
        self.ID = UUID().uuidString
    }
    
    public init(info:NSMutableDictionary) {
        let id = info["ID",""]
        if id.count < 1{
            self.ID = UUID().uuidString
        }else{
            self.ID = id
        }
        self.name = info["name",Date()["STAMP"]]
        self.locURL = info["locURL",""]
        self.remoteURL = info["remoteURL",""]
        self.progress = info["progress",""]
        self.statusJson = info["statusJson","{}"]
        self.createTime = info["createTime",""]
        self.cover = info["cover",""]
        self.initStatus()
    }
    
    
    public var raw:NSMutableDictionary{
        get{
            self.saveStatus()
            let info = [
                "name":name,
                "remoteURL":remoteURL,
                "locURL":locURL,
                "ID":ID,
                "progress":progress,
                "statusJson":statusJson,
                "createTime":createTime,
                "cover":cover
                ].mutable_dictionary
            return info
        }
    }
    
    public func save() {
        Download.save(self)
    }
    
    
    public var size:UInt64
    {
        get{
            var fileSize : UInt64 = 0
            if (FileManager.default.fileExists(atPath: locURL)){
                do {
                    let attr = try FileManager.default.attributesOfItem(atPath: locURL)
                    fileSize = attr[FileAttributeKey.size] as! UInt64

                    let dict = attr as NSDictionary
                    fileSize = dict.fileSize()
                } catch {
                    print("Error: \(error)")
                }
            }
            return fileSize
        }
    }
    
    public var m_size:String{
        get{
            let kf_m = CGFloat(size) / 1024.0 / 1024.0
            //                    let m = sd_m + kf_m
            let m = kf_m
            return "\(m.print(num: 1)) M"
        }
    }
    var description: String
    {
        return  name
    }
    
}




public struct Download {
    public static var `default` = Download()

    
    public mutating func addTask(url:String,fileName:String?=nil,cover:String?=nil) ->(task:Task,isExist:Bool){
        var ta:Task? = nil
        tasks.list { (task:Task, i) in
            if (task.file.remoteURL == url){
                ta = task
            }
        }
        
        if let task = ta{
            return (task,true)
        }
        
   
        let task = Task(url: url)
        if let name = fileName{
            task.file.name = name;
        }
        if let _cover = cover{
            task.file.cover = _cover;
        }

        task.file.save()
        tasks.add(task)
        return (task,false)
    }
    
    public mutating func removeTask(url:String) ->Task?{
        var ta:Task? = nil
        tasks.list { (task:Task, i) in
            if (task.file.remoteURL == url){
                ta = task
            }
        }
        if let t = ta{
            tasks.remove(t)
        }
        Download.save()
        return ta
    }
    
    public var tasks = NSMutableArray()
    
    
    var observerMaps:NSMutableDictionary = NSMutableDictionary()
}

extension Download{
    
    public static var fileSection:[[String:Any]]{
        get{
            let fs = files()
            return fs.map { (file) -> [String:Any] in
                return ["url":file.remoteURL]
            }
        }
    }
    
    
    public static func removeAllFileAndCache(){
        Download.default.tasks.removeAllObjects()
        UserDefaults.standard.removeObject(forKey: "download_files")
        Download._locRaw?.removeAllObjects();
        Cache.clear()
        Download.default.clearFiles()
    }
    
    public static func files()->[DownloadFile]{
        let raw = Download.locRaw
//        print("+++++++D+|\(raw)")
        var files = [DownloadFile]()
        raw.list { (obj:NSMutableDictionary, i) in
//            let loc = obj["locURL",""]
//            if loc.count > 0{
                files.append(DownloadFile(info: obj))
//            }
        }
        return files
    }
    
    public static func save(_ file:DownloadFile){
        let raw = Download.locRaw
        let info = file.raw

        var isExsit = false
        raw.list { (obj:NSMutableDictionary, i) in
            if obj["remoteURL",""] == file.remoteURL{
                isExsit = true
                obj["ID"] = info["ID",""]
                obj["name"] = info["name",""]
                obj["locURL"] = info["locURL",""]
                obj["progress"] = info["progress",""]
                obj["statusJson"] = info["statusJson",""]
                obj["createTime"] = info["createTime",""]
                obj["cover"] = info["cover",""]
            }
        }
        if !isExsit{
            raw.add(info)
        }
        UserDefaults.standard.set(raw.rawJson, forKey: "download_files")
    }
    public static func save(){
        let raw = Download.locRaw
        UserDefaults.standard.set(raw.rawJson, forKey: "download_files")
    }
    
    public static func logSavedData(){
        let cons = UserDefaults.standard.string(forKey: "download_files")?.JsonArray
        print("+++++++|\(String(describing: cons))")
        
    }
    
    public static var _locRaw:NSMutableArray? = nil
    public static var locRaw:NSMutableArray{
        get{
            if let raw = _locRaw{
                return raw
            }
            if let maps = UserDefaults.standard.string(forKey: "download_files")?.JsonArray{
                _locRaw = maps
                return maps
            }else{
                let raw = NSMutableArray()
                _locRaw = raw
                return raw
            }
        }
    }
    
    public static var downloadPath = "download_task"

    
    public func files() -> (names:[String],urls:[URL]) {
        var files:[String] = []
        var urls:[URL] = []
        let manager = FileManager.default
        let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask)
        var url = urlForDocument[0] as URL
        url.appendPathComponent(Download.downloadPath)
        if let contentsOfPath = try? manager.contentsOfDirectory(atPath: url.path){
            files = contentsOfPath
        }
        urls = files.map({ (name) -> URL in
            let u = URL(fileURLWithPath: url.path + "/" + name)
            return u
        })
        return (files,urls)
    }
    
    public func clearFiles(){
        let urls = Download.default.files().urls
        for _url in urls {
            do{
                try FileManager.default.removeItem(at: _url)
            }catch{
            }
        }
    }
}

public protocol DownloadObserver:NSObjectProtocol {
    func updateDownloadStatus(_ status:Download.Status,task:Download.Task)
}

public class DownloadObserverOwer {
    weak var ower:DownloadObserver? = nil
}


extension Download{
    
    
    public static func extensionName(url:String)->String {
        var file_extention = ""
        let reg = Regex(".+(\\.[\\w|\\d]+)$")
        if let subject = reg.firstMatch(in: url)?.captures[0] {
            file_extention = subject
        }
        return file_extention
    }
    
    public class Task {
        public let __notice = EasyNotification()
        public var deleted:Bool = false
        public var islanch:Bool = false
        public var ID = UUID().uuidString
        public var file:DownloadFile
        public var status:Status = .waiting{
            didSet{
//                self.progress(self.status)
                self.updateStatus()
            }
        }
        func updateStatus() {
            self.file.status = self.status
            switch self.status {
            case .success(code:_, message:_):
                Cache.removeCache(file: file)
            default:
                break
            }

            for observer in observers {
                observer.ower?.updateDownloadStatus(status, task: self)
            }
        }
        
        
        var resumeData: Data? = nil{
            didSet{
                saveData(data: resumeData)
            }
        }
        
        public func saveData(data:Data?) {
            if let _data = data{
//                weak var wself = self
                Download.Cache.saveCache(data: _data, file: file) { (finish) in
//                    print("|\(finish)|=====|\(wself?.file.cacheURL)")
                }
            }
        }
        
//        public var progress:(_:Status) -> Void = {_ in}
        
        public init(url:String) {
            
            self.file = DownloadFile.locFile(remoteURL: url) ?? DownloadFile()
            status = file.status
            file.remoteURL = url
            
            weak var wself = self
            __notice.observer(AppDelegateNotice.applicationDidBecomeActive,
                              AppDelegateNotice.applicationWillTerminate,
                              AppDelegateNotice.applicationWillEnterForeground,
                              AppDelegateNotice.applicationDidEnterBackground
            ) { (ntype, notice) in
                guard let type = AppDelegateNotice.notice(name: ntype.name) else {return}
                wself?.changeNotice(notice: type)
            }
        }
        
        public func changeNotice(notice:AppDelegateNotice) {
            switch notice {
            case .applicationDidBecomeActive:
                self.begin()
            case .applicationDidEnterBackground:
                self.canle()
            case .applicationWillEnterForeground:
                self.begin()
            case .applicationWillTerminate:
                break
            default:
                break
            }
        }
        
        public var stoped = false
        public var request:DownloadRequest? = nil
        public func canle()
        {
            self.stoped = true
            self.request?.cancel()
        }
        
        public func suspend(){
            self.status = .stoped
            self.request?.suspend()
            
        }
        
        
        public func begin() {
            weak var wself = self
            
            status = file.status
            self.updateStatus()
            if islanch{
                return
            }

            switch self.status {
            case .success(code:_, message: _):
                self.status = .success(code: 1, message: "已下载")
                return
            default:
                break
            }
            
            let remoteURL = file.remoteURL
            file.save()

            
            if file.locURL.len > 1{
                self.status = .success(code: 1, message: "文件已下载")
                return
            }
            
            if file.remoteURL.len < 1{
                self.status = .failed(error: Download.DownloadError.invalidURL)
                return
            }
            
            if let data = Cache.cache(for: file){
//                print("++++++hase cache")
                self.resumeData = data
            }
            
            let file_extention = Download.extensionName(url: remoteURL)
            wself?.islanch = true
            let fileName = file.ID
            let destination: DownloadRequest.DownloadFileDestination = { _, _ in
                
                let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
                let fileURL = documentsURL.appendingPathComponent("\(Download.downloadPath)/\(fileName)\(file_extention)")
                return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
            }
            
            var request: DownloadRequest
            if let resumeData = resumeData {
                request = Alamofire.download(resumingWith: resumeData,to: destination).downloadProgress { progress in
//                    if (progress.fractionCompleted > 0.3){
//                        wself?.canle()
//                        return
//                    }
                    let delete = wself?.deleted ?? true
                    if delete
                    {
                        return
                    }
                    wself?.file.progress = "\(progress.fractionCompleted.cg_floatValue)"
                    wself?.status = .processing(progress: progress.fractionCompleted)
                    wself?.file.save()

                    
                }
            } else {
                request = Alamofire.download(remoteURL,to: destination).downloadProgress { progress in
//                    if (progress.fractionCompleted > 0.3){
//                        wself?.canle()
//                        return
//                    }
                    let delete = wself?.deleted ?? true
                    if delete
                    {
                        return
                    }
                    wself?.file.progress = "\(progress.fractionCompleted.cg_floatValue)"
                    wself?.status = .processing(progress: progress.fractionCompleted)
                    wself?.file.save()
                }
            }
            
            self.request = request
            request.response { response in
                let delete = wself?.deleted ?? true
                if delete
                {
                    return
                }
                if response.error == nil, let filePath = response.destinationURL?.path {
                    wself?.file.locURL = filePath
                    wself?.status = .success(code: 2, message: "下载成功")
                    wself?.file.save()
                    wself?.islanch = false

                }else{
                    
                    wself?.islanch = false
                    wself?.resumeData = response.resumeData
                    let stop = wself?.stoped ?? false
                    if stop{
                        wself?.status = .stoped
                        wself?.file.save()
                    }else{
                        wself?.status = .failed(error: DownloadError.downloadError(message: "下载失败"))
                        wself?.file.save()
                    }
                }
            }
        }
        
        
        public func add(observer:DownloadObserver?){
            let obs = self.observers.filter({ (obs) -> Bool in
                if obs.ower == nil{
                    return false
                }
                return true
            })
            
            self.observers = obs
            
            var have = false
            for _obser in observers {
                if let obj = _obser.ower,let obj2 = observer
                {
                    if obj === obj2{
                        have = true
                    }
                }
            }
            if !have{
                let obser = DownloadObserverOwer()
                obser.ower = observer
                observers.append(obser)
            }
            self.updateStatus()
        }
        public var observers:[DownloadObserverOwer] = []
        
        public func delete(){
            self.deleted = true
            self.canle()
            let raw = Download.locRaw
            var index:Int? = nil
            let file = self.file
            raw.list { (obj:NSMutableDictionary, i) in
                if obj["remoteURL",""] == file.remoteURL{
                    index = i
                }
            }
            if let i = index{
                raw.removeObject(at: i)
            }
            
            var taskIndex:Int? = 0
            Download.default.tasks.list { (task:Task, i) in
                if file.remoteURL == task.file.remoteURL
                {
                    taskIndex = i
                }
            }
            if let i = taskIndex
            {
                Download.default.tasks.removeObject(at: i)
                Cache.removeCache(file: file)
                do{
                    try FileManager.default.removeItem(at: URL(fileURLWithPath:file.remoteURL))
                }catch{
                    
                }
                
            }
            Download.save()
            
//            print("======oooooo|\(Download.default.tasks)")
        }
        
        public var description: String
        {
            return  file.name
        }

    }
    
    
    
}

extension Download{
    public class Cache {
        
        public enum CacheSaveStatus {
            case success
            case error(error:Error)
        }
        
        
        public enum CacheFolderStatus {
            case exist
            case created
            case error(error:Error)
        }
        
        public static func clear(){
            var files:[String] = []
            let manager = FileManager.default
            
            let urlForDocument = manager.urls(for: .documentDirectory, in: .userDomainMask)
            
            var url = urlForDocument[0] as URL
            url.appendPathComponent(Cache.cach_folder)
            var urls = [URL]()
            
            if let contentsOfPath = try? manager.contentsOfDirectory(atPath: url.path){
                files = contentsOfPath
            }
            
            urls = files.map({ (name) -> URL in
                let u = URL(fileURLWithPath: url.path + "/" + name)
                return u
            })
            for _url in urls {
                do{
                    try FileManager.default.removeItem(at: _url)
                }catch{
                    
                }
            }
        }
        
        public static func cache(for file:DownloadFile)->Data?{
            var data:Data? = nil
            do{
                data = try Data(contentsOf: file.cacheURL)
            }
            catch{
                //                print("======Get cache error|\(file.cacheURL)|\(error)")
            }
            return data
        }
        
        public static func removeCache(file:DownloadFile){
            do
            {
                try FileManager.default.removeItem(at: file.cacheURL)
            }catch{
                
            }
        }
        
        public static var cach_folder:String{
            get{
                return "\(Download.downloadPath)_cache"
            }
        }
        
        public static func createFolder() ->CacheFolderStatus {
            
            let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            let cacheRootURL = documentsURL.appendingPathComponent(Cache.cach_folder)
            if FileManager.default.fileExists(atPath: cacheRootURL.path){
                return .exist
            }else{
                
                do{
                    try FileManager.default.createDirectory(at: cacheRootURL, withIntermediateDirectories: true, attributes: nil)
                    return .created
                }catch{
                    return .error(error: error)
                }
            }
            
            
        }
        public static func saveCache(data:Data,file:DownloadFile,finished:@escaping (CacheSaveStatus)->Void){
            
            let status = createFolder()
            switch status {
            case .created,.exist:
                break
            case .error(error: let error):
                finished(.error(error: error))
                return
            }
            
            DispatchQueue(label: "Download_Cache_saveCache").async {
                do{
                    try data.write(to: file.cacheURL)
                    DispatchQueue.main.async(execute: {
                        finished(.success)
                    })
                }catch{
                    finished(.error(error: error))
                }
            }
        }
    }
}
